package top.techqi.usbd.util

import android.hardware.usb.UsbConstants
import android.hardware.usb.UsbDeviceConnection
import top.techqi.core.kext.DWORD_T
import top.techqi.usbd.desc.AbstractDesc
import top.techqi.usbd.desc.ConfigDesc
import top.techqi.usbd.desc.DeviceDesc
import top.techqi.usbd.desc.EdptDesc
import top.techqi.usbd.desc.IADDesc
import top.techqi.usbd.desc.IntfDesc
import top.techqi.usbd.desc.LangidDesc
import top.techqi.usbd.desc.StringDesc
import top.techqi.usbd.desc.ZerothDesc
import java.util.Locale

object DescInflater {
    const val BYTE_0: Byte = 0
    const val REQUEST_GET_DESCRIPTOR = 0x06

    fun inflate(conn: UsbDeviceConnection): List<AbstractDesc> {
        val langid = pickLangid(inflateLangid(conn))
        val descriptors = mutableListOf<AbstractDesc>()

        val raw = conn.rawDescriptors
        var lastIntf: IntfDesc? = null
        var position = 0
        while (position < raw.size) {
            val length = raw[position]
            val boundary = position + length
            val slice = raw.sliceArray(position..<boundary)
            when (slice[1]) {
                DeviceDesc.TYPE -> {
                    descriptors += DeviceDesc(
                        slice,
                        inflateString(conn, slice[14], langid),
                        inflateString(conn, slice[15], langid),
                        inflateString(conn, slice[16], langid)
                    )
                }

                ConfigDesc.TYPE -> {
                    descriptors += ConfigDesc(slice)
                }

                IADDesc.TYPE -> {
                    descriptors += IADDesc(
                        slice,
                        inflateString(conn, slice[7], langid)
                    )
                }

                IntfDesc.TYPE -> {
                    lastIntf = IntfDesc(
                        slice,
                        inflateString(conn, slice[8], langid)
                    )
                    descriptors += lastIntf
                }

                IntfDesc.CSDesc.TYPE -> {
                    lastIntf?.let {
                        it.specs = arrayOf(*it.specs, IntfDesc.CSDesc(slice))
                    }
                }

                EdptDesc.TYPE -> {
                    descriptors += EdptDesc(slice)
                }

                else -> {
                    descriptors += ZerothDesc(slice)
                }
            }
            position = boundary
        }

        return descriptors
    }

    fun inflateLangid(conn: UsbDeviceConnection): ShortArray {
        val buffer = ByteArray(256)
        val result = conn.controlTransfer(
            UsbConstants.USB_DIR_IN or UsbConstants.USB_TYPE_STANDARD,
            REQUEST_GET_DESCRIPTOR,
            DWORD_T(0, 0, LangidDesc.TYPE, 0x00), 0,
            buffer, buffer.size, 500
        )
        if (result <= 0) return ShortArray(0)
        return LangidDesc(buffer).wLANGID
    }

    fun inflateString(conn: UsbDeviceConnection, index: Byte, langid: Short): String? {
        // 0x00 is reserved for no string descriptor
        if (index == BYTE_0) return null

        val buffer = ByteArray(256)
        val result = conn.controlTransfer(
            UsbConstants.USB_DIR_IN or UsbConstants.USB_TYPE_STANDARD,
            REQUEST_GET_DESCRIPTOR,
            DWORD_T(0, 0, StringDesc.TYPE, index),
            DWORD_T(0, langid),
            buffer, buffer.size, 500
        )
        if (result <= 0) return null
        return StringDesc(buffer).string
    }

    private fun pickLangid(langids: ShortArray): Short {
        val locale = Locale.getDefault()
        return when (locale.language) {
            "zh" -> {
                when (locale.country) {
                    "CN" -> arrayOf(
                        LangidDesc.LANGID_ZH_CN,
                        LangidDesc.LANGID_ZH_SG,
                        LangidDesc.LANGID_ZH_TW,
                        LangidDesc.LANGID_ZH_HK,
                        LangidDesc.LANGID_EN_US
                    ).firstOrNull { it in langids }

                    else -> null
                }
            }

            else -> null
        } ?: langids.first()
    }
}
